package org.eclipse.jdt.core;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.ClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.IProblemFactory;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;

import x.java.io.File;
import x.java.io.FileInputStream;
import x.java.io.FileOutputStream;
import x.java.io.FileReader;

/**
 * @author qiangli
 * 
 */
public class JavaCompiler {
	public interface Logger {
		public void debug(String s);
		public void error(Exception e);
		public void info(String s);
		public void problem(IProblem p);
		//public void problems(ArrayList<IProblem> problemList);
	}
	
	class CompilationUnit implements ICompilationUnit {
		protected String className;
		protected String encoding;
		protected String fileName;

		public CompilationUnit(String fileName, String encoding,
				String className) {
			this.className = className;
			this.encoding = encoding;
			this.fileName = fileName;
		}

		public String getClassName() {
			return className;
		}

		public char[] getContents() {
			InputStream is = null;
			try {
				is = getInputStream();
				return getContents(is);
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				if (is != null) {
					try {
						is.close();
					} catch (IOException exc) {
						// Ignore
					}
				}
			}
			return null;
		}

		protected char[] getContents(InputStream is) throws IOException {
			char[] result = null;
			Reader reader = new BufferedReader(new InputStreamReader(is,
					encoding), File.BUFSIZE);
			if (reader != null) {
				char[] chars = new char[1024];
				StringBuffer buf = new StringBuffer();
				int count;
				while ((count = reader.read(chars, 0, chars.length)) > 0) {
					buf.append(chars, 0, count);
				}
				result = new char[buf.length()];
				buf.getChars(0, result.length, result, 0);
			}
			return result;
		}

		public char[] getFileName() {
			return fileName.toCharArray();
		}

		protected InputStream getInputStream() throws IOException {
			return new FileInputStream(fileName);
		}

		public char[] getMainTypeName() {
			int dot = className.lastIndexOf('.');
			if (dot > 0) {
				return className.substring(dot + 1).toCharArray();
			}
			return className.toCharArray();
		}

		public char[][] getPackageName() {
			StringTokenizer izer = new StringTokenizer(className, ".");
			char[][] result = new char[izer.countTokens() - 1][];
			for (int i = 0; i < result.length; i++) {
				String tok = izer.nextToken();
				result[i] = tok.toCharArray();
			}
			return result;
		}
	}

	abstract class CompilerRequestor implements ICompilerRequestor {

		protected String output = null;
		protected List<IProblem> problems = null;

		protected ZipOutputStream srczos = null;

		public CompilerRequestor(String output, List<IProblem> problems,
				String srcout) throws IOException {
			this.output = output;
			this.problems = problems;
			if (srcout != null) {
				srczos = new ZipOutputStream(new FileOutputStream(srcout));
			}
		}

		public void acceptResult(CompilationResult result) {
			try {
				if (result.hasProblems()) {
					IProblem[] l = result.getProblems();
					for (int i = 0; i < l.length; i++) {
						IProblem problem = l[i];
						if (problem.isError()) {
							try {
								problems.add(problem);
								problem(problem);
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}
				}
				if (problems.isEmpty()) {
					ClassFile[] classFiles = result.getClassFiles();
					writeClass(classFiles);
					if (srczos != null) {
						writeJava((CompilationUnit)result.getCompilationUnit());
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		protected String filename(ClassFile file) {
			char[][] compoundName = file.getCompoundName();
			String className = "";
			String sep = "";
			for (int j = 0; j < compoundName.length; j++) {
				className += sep;
				className += new String(compoundName[j]);
				sep = ".";
			}
			String name = className.replace('.', '/') + ".class";
			return name;
		}

		public void finish() {
			try {
				if (srczos != null) {
					srczos.close();
				}
			} catch (Exception e) {
			}
		}

		abstract protected void writeClass(ClassFile[] files)
				throws IOException;

		protected void writeJava(CompilationUnit cu) throws IOException {
			String name = cu.getClassName().replace('.', '/')+".java";
			InputStream in = cu.getInputStream();
			//
			ZipEntry ze = new ZipEntry(name);
			srczos.putNextEntry(ze);
			byte[] b = new byte[1024];
			int read = -1;
			while ((read = in.read(b)) != -1) {
				srczos.write(b, 0, read);
			}
			srczos.closeEntry();
			//
			in.close();
			debug("source entry written: " + name);
		}
	}

	class FileCompilerRequestor extends CompilerRequestor {

		public FileCompilerRequestor(String output, List<IProblem> problems,
				String srcout) throws IOException {
			super(output, problems, srcout);
		}

		@Override
		protected void writeClass(ClassFile[] files) throws IOException {
			for (int i = 0; i < files.length; i++) {
				ClassFile classFile = files[i];
				String name = filename(classFile);
				byte[] bytes = classFile.getBytes();
				File outFile = new File(output, "/" + name);
				outFile.getParentFile().mkdirs();//
				FileOutputStream fout = new FileOutputStream(outFile);
				BufferedOutputStream bos = new BufferedOutputStream(fout);
				bos.write(bytes);
				bos.close();
				//
				debug("file written: " + outFile);
			}
		}
	}

	class JarCompilerRequestor extends CompilerRequestor {
		FileOutputStream fos = null;
		JarOutputStream jos = null;

		public JarCompilerRequestor(String output, List<IProblem> problems,
				String srcout) throws IOException {
			super(output, problems, srcout);
			fos = new FileOutputStream(output);
			jos = new JarOutputStream(fos);
		}

		public void finish() {
			super.finish();
			try {
				jos.close();
			} catch (IOException e) {
			}
			try {
				fos.close();
			} catch (IOException e) {
			}
		}

		@Override
		protected void writeClass(ClassFile[] files) throws IOException {
			for (int i = 0; i < files.length; i++) {
				ClassFile cf = files[i];
				String name = filename(cf);
				byte[] bytes = cf.getBytes();
				//
				JarEntry je = new JarEntry(name);
				jos.putNextEntry(je);
				jos.write(bytes);
				jos.closeEntry();
				//
				debug("jar entry written: " + name);
			}
		}
	}

	class NameEnvironment implements INameEnvironment {
		private CompilationUnit[] optional = null;
		private Classpath[] path = null;
		private CompilationUnit[] units = null;

		public NameEnvironment(Classpath[] path, CompilationUnit[] units)
				throws IOException {
			this.path = path;
			this.units = units;
			this.optional = null;
		}

		public NameEnvironment(Classpath[] path, CompilationUnit[] units,
				CompilationUnit[] optional) throws IOException {
			this.path = path;
			this.units = units;
			this.optional = optional;
		}

		public void cleanup() {
			// delete class path cached files?
		}

		public NameEnvironmentAnswer findType(char[] typeName,
				char[][] packageName) {
			String result = "";
			String sep = "";
			for (int i = 0; i < packageName.length; i++) {
				result += sep;
				result += new String(packageName[i]);
				sep = ".";
			}
			result += sep;
			result += new String(typeName);
			return findType(result);
		}

		public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
			String result = "";
			String sep = "";
			for (int i = 0; i < compoundTypeName.length; i++) {
				result += sep;
				result += new String(compoundTypeName[i]);
				sep = ".";
			}
			return findType(result);
		}

		private NameEnvironmentAnswer findType(String className) {
			InputStream is = null;
			try {
				for (CompilationUnit cu : units) {
					if (className.equals(cu.getClassName())) {
						return new NameEnvironmentAnswer(cu, null);
					}
				}
				String resource = className.replace('.', '/');
				//
				is = getResourceAsStream(resource + ".class");
				if (is != null) {
					byte[] classBytes;
					byte[] buf = new byte[1024];
					ByteArrayOutputStream baos = new ByteArrayOutputStream(
							buf.length);
					int count;
					while ((count = is.read(buf, 0, buf.length)) > 0) {
						baos.write(buf, 0, count);
					}
					baos.flush();
					classBytes = baos.toByteArray();
					// .class
					char[] fileName = className.toCharArray();
					ClassFileReader classFileReader = new ClassFileReader(
							classBytes, fileName, true);
					return new NameEnvironmentAnswer(classFileReader, null);
				}
				//
				if (optional != null) {
					for (CompilationUnit cu : optional) {
						if (className.equals(cu.getClassName())) {
							debug("optional: " + resource);
							return new NameEnvironmentAnswer(cu, null);
						}
					}
				}
				//
				// TODO add optional 3rd library option and generate mininal jar
				// file
				//
			} catch (Exception e) {
				e.printStackTrace();
			} finally {

				if (is != null) {
					try {
						is.close();
					} catch (IOException exc) {
						// Ignore
					}
				}

			}
			return null;
		}

		private InputStream getResourceAsStream(String name) {
			InputStream is = null;
			for (Classpath cp : path) {
				try {
					is = cp.getResourceAsStream(name);
					if (is != null) {
						return is;
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			//Use system classes?
			System.err.println("class: "+name);
			is = getClass().getResourceAsStream(name);
			if (is == null) {
				System.err.println("context: "+name);
				ClassLoader cl = Thread.currentThread().getContextClassLoader();
				if (cl != null) {
					is = cl.getResourceAsStream(name);
				}
			}
			//if (is == null) {
				//System.err.println("vmstack: "+name);
				//ClassLoader cl = VMStack.getCallingClassLoader();
				//if (cl != null) {
				//	is = cl.getResourceAsStream(name);
				//}
			//}
			if (is == null) {
				System.err.println("system: "+name);
				is = ClassLoader.getSystemResourceAsStream(name);
			}
			System.err.println("@@@exit:  "+is+" name: "+name);
			//
			return is;
		}

		public boolean isPackage(char[][] parentPackageName, char[] packageName) {
			String result = "";
			String sep = "";
			if (parentPackageName != null) {
				for (int i = 0; i < parentPackageName.length; i++) {
					result += sep;
					String str = new String(parentPackageName[i]);
					result += str;
					sep = ".";
				}
			}
			String str = new String(packageName);
			if (Character.isUpperCase(str.charAt(0))) {
				if (!isPackage(result)) {
					return false;
				}
			}
			result += sep;
			result += str;
			return isPackage(result);
		}

		private boolean isPackage(String result) {
			for (int i = 0; i < units.length; i++) {
				if (result.equals(units[i].getClassName())) {
					return false;
				}
			}
			String resource = result.replace('.', '/') + ".class";
			return (getResourceAsStream(resource) == null);
		}
	}

	class ZipCompilationUnit extends CompilationUnit {
		protected ZipFile zip;

		public ZipCompilationUnit(String fileName, String encoding,
				String className, ZipFile zip) {
			super(fileName, encoding, className);
			this.zip = zip;
		}

		@Override
		protected InputStream getInputStream() throws IOException {
			ZipEntry ze = zip.getEntry(fileName);
			return zip.getInputStream(ze);
		}
	}

	class ZipCompilerRequestor extends CompilerRequestor {
		FileOutputStream fos = null;
		ZipOutputStream zos = null;

		public ZipCompilerRequestor(String output, List<IProblem> problems,
				String srcout) throws IOException {
			super(output, problems, srcout);
			fos = new FileOutputStream(output);
			zos = new ZipOutputStream(fos);
		}

		public void finish() {
			super.finish();
			try {
				zos.close();
			} catch (IOException e) {
			}
			try {
				fos.close();
			} catch (IOException e) {
			}
		}

		@Override
		protected void writeClass(ClassFile[] files) throws IOException {
			for (int i = 0; i < files.length; i++) {
				ClassFile cf = files[i];
				String name = filename(cf);
				byte[] bytes = cf.getBytes();
				//
				ZipEntry ze = new ZipEntry(name);
				zos.putNextEntry(ze);
				zos.write(bytes);
				zos.closeEntry();
				//
				debug("zip entry written: " + name);
			}
		}
	}

	public static void main(String[] args) throws IOException {

		if (args.length == 0) {
			usage();
			return;
		}

		//List<URL> cp = new ArrayList<URL>();
		List<File> cp = new ArrayList<File>();
		List<String> src = new ArrayList<String>();
		List<String> opt = new ArrayList<String>();
		String lst = null;

		String out = null;
		boolean force = false;
		boolean verbose = false;
		boolean keepall = false;

		String source = "1.5";
		String target = "1.5";
		String compliance = "1.5";
		String encoding = "UTF-8";

		String res = null;
		List<String> incl = new ArrayList<String>();
		List<String> excl = new ArrayList<String>();
		
		String save = null;
		for (int i = 0; i < args.length; i++) {
			if ("-cp".equals(args[i])) {
				//cp.add(new URL(args[++i]));
				cp.add(new File(args[++i]));
			} else if ("-src".equals(args[i])) {
				src.add(args[++i]);
			} else if ("-opt".equals(args[i])) {
				opt.add(args[++i]);
			} else if ("-list".equals(args[i])) {
				lst = args[++i];
			} else if ("-out".equals(args[i])) {
				out = args[++i];
			} else if ("-source".equals(args[i])) {
				source = args[++i];
			} else if ("-target".equals(args[i])) {
				target = args[++i];
			} else if ("-compliance".equals(args[i])) {
				compliance = args[++i];
			} else if ("-encoding".equals(args[i])) {
				encoding = args[++i];
			} else if ("-verbose".equals(args[i])) {
				verbose = true;
			} else if ("-force".equals(args[i])) {
				force = true;
			} else if ("-keepall".equals(args[i])) {
				keepall = true;
			} else if ("-res".equals(args[i])) {
				res = args[++i];
			} else if ("-incl".equals(args[i])) {
				incl.add(args[++i]);
			} else if ("-excl".equals(args[i])) {
				excl.add(args[++i]);
			} else if ("-save".equals(args[i])) {
				save = args[++i];
			} else if ("-help".equals(args[i])) {
				usage();
				return;
			} else {
				usage();
				return;
			}
		}
		if (out == null || (src.size() == 0 && lst == null)) {
			usage();
			return;
		}
		JavaCompiler jc = new JavaCompiler(cp.toArray(new File[0]));
		// compile
		jc.setForce(force);
		jc.setVerbose(verbose);
		jc.setKeepall(keepall);
		
		jc.setSource(source);
		jc.setTarget(target);
		jc.setCompliance(compliance);
		jc.setEncoding(encoding);
		long rc = jc.xcompile(src.toArray(new String[0]), opt.toArray(new String[0]),
				lst, out,save);
		// copy resource
		if (rc != -1 && res != null) {
			List<String> srcopt = new ArrayList<String>();
			srcopt.addAll(src);
			srcopt.addAll(opt);
			jc.copyResource(srcopt.toArray(new String[0]),
					incl.size() == 0 ? null : incl.toArray(new String[0]), excl
							.size() == 0 ? null : excl.toArray(new String[0]),
					res);
		}
	}
	
	public static void usage() {
		System.out
				.println("args: -cp <classpath URL>... -src <source zip or directory> ... -list <src list> -out <output zip or directory>");
		System.out
				.println("names ending in .zip or .jar are assumed to be zip files");
		System.out
				.println("additional compilation options: -source <1.5> -target <1.5> -compliance <1.5> -verbose -encoding <UTF-8> ");

		System.out
				.println("resource options: -res <filename> -incl <includes> -excl <excludes>");
	}

	protected Classpath[] classpath = null;

	protected String compliance = CompilerOptions.VERSION_1_5;
	
	protected boolean debuginfo = true;
	
	protected String encoding = "UTF-8";
	
	protected boolean force = false;
	
	protected boolean keepall = false;//donot include classpath classes

	protected String source = CompilerOptions.VERSION_1_5;

	protected String target = CompilerOptions.VERSION_1_5;

	protected boolean verbose = true;

	public JavaCompiler() {
		this.classpath = new Classpath[0];
	}

	public JavaCompiler(File[] files) throws IOException {
		this.classpath = new Classpath[files.length];
		for (int i = 0; i < classpath.length; i++) {
			classpath[i] = new Classpath(files[i]);
		}
	}

	public long compile(File src, File outdir)  throws IOException {
		return compile(new String[] {src.getPath()}, outdir.getPath());
	}

	public long compile(String fileName, String className, String out)
			throws Exception {
		return compile(new String[] { fileName }, new String[] { className },
				out);
	}

	public long compile(String[] src, String outdir) throws IOException {
		List<CompilationUnit> units = new ArrayList<CompilationUnit>();
		for (int i = 0; i < src.length; i++) {
			File root = new File(src[i]);
			if (root.isDirectory()) {
				long idx = root.getCanonicalPath().length() + 1;
				scan((int) idx, root, units, outdir);
			} else {
				File of = new File(outdir);
				if (isForce() || root.lastModified() > of.lastModified()) {
					scan(new ZipFile(root), units);
				}
			}
		}

		if (units.size() > 0) {
			info("Compiling total: " + units.size());

			CompilationUnit[] cu = units.toArray(new CompilationUnit[0]);
			NameEnvironment env = new NameEnvironment(classpath, cu.clone());
			return generateClass(cu, env, outdir,null);
		} else {
			info("No new files to compile.");
		}
		return -1;
	}

	public long compile(String[] fileNames, String[] classNames, String out)
			throws Exception {
		CompilationUnit[] cu = new CompilationUnit[classNames.length];
		for (int i = 0; i < cu.length; i++) {
			cu[i] = new CompilationUnit(fileNames[i], encoding, classNames[i]);
		}
		NameEnvironment env = new NameEnvironment(classpath, cu.clone());
		return generateClass(cu, env, out,null);
	}

	private boolean contains(Collection<String> list, String name) {
		for (String s : list) {
			if (s.endsWith("*")) {
				s = s.substring(0, s.length() - 1);
				if (name.startsWith(s)) {
					return true;
				}
			} else {
				if (name.equals(s)) {
					return true;
				}
			}
		}
		return false;
	}

	public void copyResource(String[] src, String[] includes,
			String[] excludes, String zip) throws IOException {
		List<String> res = new ArrayList<String>();
		FileOutputStream fos = new FileOutputStream(zip);
		ZipOutputStream zos = new ZipOutputStream(fos);
		for (int i = 0; i < src.length; i++) {
			res.clear();
			File root = new File(src[i]);
			if (root.isDirectory()) {
				long idx = root.getCanonicalPath().length() + 1;
				scanResource((int) idx, root, res, includes, excludes);
				//
				for (String r : res) {
					try {
						zos.putNextEntry(new ZipEntry(r));
						//
						File f = new File(root, r);
						FileInputStream in = new FileInputStream(f);
						byte[] b = new byte[1024];
						int read = -1;
						while ((read = in.read(b)) != -1) {
							zos.write(b, 0, read);
						}
						zos.closeEntry();
						in.close();
					} catch (Exception e) {
						error(e);
					}
				}
			} else {
				scanResource(new ZipFile(root), res, includes, excludes);
				//
				ZipFile zf = new ZipFile(root);
				for (String r : res) {
					try {
						zos.putNextEntry(new ZipEntry(r));
						ZipEntry ze = zf.getEntry(r);
						InputStream in = zf.getInputStream(ze);
						byte[] b = new byte[1024];
						int read = -1;
						while ((read = in.read(b)) != -1) {
							zos.write(b, 0, read);
						}
						zos.closeEntry();
					} catch (Exception e) {
						error(e);
					}
				}
				zf.close();
			}
		}
		zos.close();
		info("Resource saved as " + zip);
	}

	protected void debug(String s) {
		System.out.println(s);
	}

	protected void error(Exception e) {
		System.out.println(e);
	}

	protected void finish() {
		//
	}

	@SuppressWarnings("deprecation")
	protected long generateClass(CompilationUnit[] cu, NameEnvironment env,
			String outdir,String srcout) throws IOException {
		long t1 = System.currentTimeMillis();
		//
		IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies
				.proceedWithAllProblems();

		Map<String, String> settings = new HashMap<String, String>();
		settings.put(CompilerOptions.OPTION_LineNumberAttribute,
				CompilerOptions.GENERATE);
		settings.put(CompilerOptions.OPTION_SourceFileAttribute,
				CompilerOptions.GENERATE);
		settings.put(CompilerOptions.OPTION_ReportDeprecation,
				CompilerOptions.IGNORE);

		settings.put(CompilerOptions.OPTION_Encoding, encoding);

		if (debuginfo) {
			settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
					CompilerOptions.GENERATE);
		}

		// Source JVM
		settings.put(CompilerOptions.OPTION_Source, source);

		// Target JVM
		settings.put(CompilerOptions.OPTION_TargetPlatform, target);
		if (compliance != null) {
			settings.put(CompilerOptions.OPTION_Compliance, compliance);
		}

		ArrayList<IProblem> problemList = new ArrayList<IProblem>();

		final IProblemFactory problemFactory = new DefaultProblemFactory(Locale
				.getDefault());

		final CompilerRequestor requestor;
		if (outdir.endsWith("zip")) {
			requestor = new ZipCompilerRequestor(outdir, problemList,srcout);
		} else if (outdir.endsWith("jar")) {
			requestor = new JarCompilerRequestor(outdir, problemList,srcout);
		} else {// directory
			requestor = new FileCompilerRequestor(outdir, problemList,srcout);
		}

		Compiler compiler = new Compiler(env, policy, settings, requestor,
				problemFactory, true);

		//
		compiler.compile(cu);

		requestor.finish();
		
		long t2 = System.currentTimeMillis();
		long elapsed = (t2 - t1);
		if (!problemList.isEmpty()) {
			elapsed = -1;
			problems(problemList);
		} else {
			info("Success! Compiled " + cu.length + " files in " + (t2 - t1)
					+ "ms");
			info("Output: " + outdir);
		}
		finish();
		return elapsed;
	}

	public String getEncoding() {
		return encoding;
	}

	public String getSource() {
		return source;
	}

	public String getTarget() {
		return target;
	}

	protected void info(String s) {
		System.out.println(s);
	}

	public boolean isDebuginfo() {
		return debuginfo;
	}

	public boolean isForce() {
		return force;
	}

	public boolean isKeepall() {
		return keepall;
	}

	public boolean isVerbose() {
		return verbose;
	}
	
	protected void problems(ArrayList<IProblem> problemList) {
		//
	}

	protected void problem(IProblem p) {
		System.out.println(p);
	}

	private Collection<String> readList(String file) throws IOException {
		Set<String> list = new TreeSet<String>();
		if (file == null) {
			return list;
		}
		FileReader r = new FileReader(file);
		BufferedReader br = new BufferedReader(r, File.BUFSIZE); //215
		String l = null;
		while ((l = br.readLine()) != null) {
			l = l.trim();
			if (l.length() < 1) {
				continue;
			}
			if (l.startsWith("//") || l.startsWith("#")) {
				// ignore comments
				continue;
			}
			list.add(l);
		}
		br.close();
		r.close();

		return list;
	}

	private void scan(int beginIndex, File dir, List<CompilationUnit> units,
			String outdir) throws IOException {
		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory()) {
				scan(beginIndex, files[i], units, outdir);
			} else if (files[i].isFile()
					&& files[i].getName().endsWith(".java")) {
				String fn = files[i].getCanonicalPath();
				int idx = fn.lastIndexOf(".");
				File cf = new File(outdir, fn.substring(beginIndex, idx)
						+ ".class");
				if (isForce() || files[i].lastModified() > cf.lastModified()) {
					String cn = fn.substring(beginIndex, idx).replace(
							File.separatorChar, '.');
					CompilationUnit cu = new CompilationUnit(fn, encoding, cn);
					units.add(cu);
					debug("added source file: " + fn + " " + cn);
				}
			}
		}
	}

	private void scan(ZipFile zip, List<CompilationUnit> units)
			throws IOException {
		for (Enumeration<? extends ZipEntry> en = zip.entries(); en
				.hasMoreElements();) {
			ZipEntry ze = en.nextElement();
			if (ze.isDirectory()) {
				continue;
			}
			String fn = ze.getName();
			if (!fn.endsWith(".java")) {
				continue;
			}
			int idx = fn.lastIndexOf(".");
			String cn = fn.substring(0, idx).replace('/', '.');
			CompilationUnit cu = new ZipCompilationUnit(fn, encoding, cn, zip);
			units.add(cu);

			debug("added zip entry: " + fn + " " + cn);
		}
	}

	private void scanResource(int beginIndex, File dir, List<String> res,
			String[] includes, String[] excludes) throws IOException {
		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory()) {
				scanResource(beginIndex, files[i], res, includes, excludes);
			} else if (files[i].isFile()) {
				String fn = files[i].getCanonicalPath();
				if (fn.endsWith(".class")) { // ignore class file
					continue;
				}
				fn = fn.substring(beginIndex).replace(File.separatorChar, '/');
				if (wanted(fn, includes, excludes)) {
					res.add(fn);
					debug("added resource: " + fn);
				}
			}
		}
	}

	private void scanResource(ZipFile zip, List<String> res, String[] includes,
			String[] excludes) throws IOException {
		for (Enumeration<? extends ZipEntry> en = zip.entries(); en
				.hasMoreElements();) {
			ZipEntry ze = en.nextElement();
			if (ze.isDirectory()) {
				continue;
			}
			String fn = ze.getName();
			if (fn.endsWith(".class")) { // ignore class file
				continue;
			}
			if (wanted(fn, includes, excludes)) {
				res.add(fn);

				debug("added zip resource entry: " + fn);
			}
		}
	}

	public void setCompliance(String opt) {

		if (opt.equals("1.1")) {
			compliance = CompilerOptions.VERSION_1_1;
		} else if (opt.equals("1.2")) {
			compliance = CompilerOptions.VERSION_1_2;
		} else if (opt.equals("1.3")) {
			compliance = CompilerOptions.VERSION_1_3;
		} else if (opt.equals("1.4")) {
			compliance = CompilerOptions.VERSION_1_4;
		} else if (opt.equals("1.5")) {
			compliance = CompilerOptions.VERSION_1_5;
		} else if (opt.equals("1.6")) {
			compliance = CompilerOptions.VERSION_1_6;
		} else if (opt.equals("1.7")) {
			compliance = CompilerOptions.VERSION_1_7;
		} else {
			info("Unknown target VM " + opt + " ignored.");
		}
	}

	public void setDebuginfo(boolean debuginfo) {
		this.debuginfo = debuginfo;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	public void setForce(boolean force) {
		this.force = force;
	}

	public void setKeepall(boolean keepall) {
		this.keepall = keepall;
	}

	public void setSource(String opt) {
		if (opt.equals("1.1")) {
			source = CompilerOptions.VERSION_1_1;
		} else if (opt.equals("1.2")) {
			source = CompilerOptions.VERSION_1_2;
		} else if (opt.equals("1.3")) {
			source = CompilerOptions.VERSION_1_3;
		} else if (opt.equals("1.4")) {
			source = CompilerOptions.VERSION_1_4;
		} else if (opt.equals("1.5")) {
			source = CompilerOptions.VERSION_1_5;
		} else if (opt.equals("1.6")) {
			source = CompilerOptions.VERSION_1_6;
		} else if (opt.equals("1.7")) {
			source = CompilerOptions.VERSION_1_7;
		} else {
			info("Unknown source VM " + opt + " ignored.");
		}
	}

	public void setTarget(String opt) {

		if (opt.equals("1.1")) {
			target = CompilerOptions.VERSION_1_1;
		} else if (opt.equals("1.2")) {
			target = CompilerOptions.VERSION_1_2;
		} else if (opt.equals("1.3")) {
			target = CompilerOptions.VERSION_1_3;
		} else if (opt.equals("1.4")) {
			target = CompilerOptions.VERSION_1_4;
		} else if (opt.equals("1.5")) {
			target = CompilerOptions.VERSION_1_5;
			compliance = CompilerOptions.VERSION_1_5;
		} else if (opt.equals("1.6")) {
			target = CompilerOptions.VERSION_1_6;
		} else if (opt.equals("1.7")) {
			target = CompilerOptions.VERSION_1_7;
		} else {
			info("Unknown target VM " + opt + " ignored.");
		}
	}

	public void setVerbose(boolean verbose) {
		this.verbose = verbose;
	}

	private boolean wanted(String name, String[] includes, String[] excludes) {
		if (includes == null && excludes == null) {
			// default to including everything
			return true;
		}
		if (excludes != null) {
			for (String s : excludes) {
				if (name.matches(s)) {
					return false;
				}
			}
		}
		if (includes != null) {
			for (String s : includes) {
				if (name.matches(s)) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

	public long xcompile(String[] src, String[] optional, String list,
			String outdir, String srcout) throws IOException {
		List<CompilationUnit> units = new ArrayList<CompilationUnit>();
		for (int i = 0; i < src.length; i++) {
			File root = new File(src[i]);
			if (root.isDirectory()) {
				long idx = root.getCanonicalPath().length() + 1;
				scan((int) idx, root, units, outdir);
			} else {
				File of = new File(outdir);
				if (isForce() || root.lastModified() > of.lastModified()) {
					scan(new ZipFile(root), units);
				}
			}
		}

		List<CompilationUnit> optunits = new ArrayList<CompilationUnit>();
		for (int i = 0; i < optional.length; i++) {
			File root = new File(optional[i]);
			if (root.isDirectory()) {
				long idx = root.getCanonicalPath().length() + 1;
				scan((int) idx, root, optunits, outdir);
			} else {
				File of = new File(outdir);
				if (isForce() || root.lastModified() > of.lastModified()) {
					scan(new ZipFile(root), optunits);
				}
			}
		}
		//
		// check list
		Collection<String> lst = readList(list);
		for (CompilationUnit cu : optunits) {
			if (contains(lst, cu.getClassName())) {
				units.add(cu);
				debug("added from list: " + cu.getClassName());
			}
		}
		//remove cu which is available on classpath
		//if keepall is false 
		if (!keepall) {
			for (Iterator<?> i = units.iterator(); i.hasNext();) {
				CompilationUnit cu = (CompilationUnit) i.next();
				for (Classpath cp: classpath) {
					String resource = cu.getClassName().replace('.', '/') + ".class";
					if (cp.findResource(resource) != null) {
						i.remove();
						debug("removed from compilation: " + resource);
					}
				}
			}
		}
		//
		if (units.size() > 0) {
			info("Compiling total: " + units.size());
			info("Optional total: " + optunits.size());

			CompilationUnit[] cu = units.toArray(new CompilationUnit[0]);
			CompilationUnit[] ocu = optunits.toArray(new CompilationUnit[0]);

			NameEnvironment env = new NameEnvironment(classpath, cu.clone(),
					ocu.clone());
			long t = generateClass(cu, env, outdir,srcout);
			// zip source
			//
			return t;
		} else {
			info("No new files to compile.");
		}
		return -1;
	}
}